import type { AxiosResponse, AxiosError, AxiosRequestHeaders } from 'axios';
import axios from 'axios';
import { Notification } from '@arco-design/web-vue';
import { type Ref } from 'vue';
import { isAuthFailedCode } from '@/common/utils/auth';
import { getLanguageFromClient } from '@/common/utils/language';
import { getTokenFromClient, clearTokenForClient } from '@/common/utils/auth';
import { getCommunityNameFromClient } from '@/common/utils/community';
import i18n from '@/settings/i18n';

export declare interface FetchOptions {
  method?: string,
  upload?: boolean,
  data?: any,
  json?: boolean,
  cache?: boolean,
  headers?: any,
  errorMessage?: string | boolean,
  loadingStatus?: Ref<boolean>,
  onUploadProgress?: (progress: any) => void,
  onUploadPercent?: (percent: number, progressEvent: any) => void,
}

export declare interface FetchResult<ResT> {
  isSuccess: boolean,
  code: number | string,
  msg?: string,
  data?: ResT,
  $response?: AxiosResponse | AxiosError | Error,
}

const DEFAULT_ERROR_CODE = 999999;

// 创建 axios 实例
const instance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 30000,
});

// request 拦截器
instance.interceptors.request.use(config => {
  if (config.method === 'get') {
    config.params = config.data;
  }

  if (!config.headers) {
    config.headers = {} as AxiosRequestHeaders;
  }

  config.headers['X-ATOMGIT-POP-COMMUNITY'] = getCommunityNameFromClient();
  config.headers['X-ATOMGIT-POP-LANGUAGE'] = getLanguageFromClient();

  const token = getTokenFromClient();
  token && (config.headers['X-ATOMGIT-POP-TOKEN'] = token);

  return config;
}, error => {
  // Do something with request error
  console.error(error); // for debug
  return Promise.reject(error);
});

export async function $fetch<ResT = any>(url: string, inputOptions: FetchOptions = {}): Promise<FetchResult<ResT>> {
  const options = { ...inputOptions };

  if (!options.method) {
    options.method = 'get';
  }

  if (!options.data) {
    options.data = {};
  }

  if (!options.headers) {
    options.headers = {};
  }

  if (options.upload === true) {
    const fd = new FormData();

    for (const key in options.data) {
      if (Object.prototype.hasOwnProperty.call(options.data, key)) {
        const value = options.data[key];

        if (value === undefined) {
          continue;
        }

        if (Array.isArray(value)) {
          value.forEach((item, index) => {
            if (typeof item === 'object') {
              for (const subKey in item) {
                if (Object.prototype.hasOwnProperty.call(item, subKey)) {
                  fd.append(`${key}[${index}][${subKey}]`, item[subKey]);
                }
              }
            }
            else {
              fd.append(`${key}[]`, item);
            }
          });
        }
        else {
          fd.append(key, value);
        }
      }
    }

    options.data = fd;
  }
  else if (options.method === 'post') {
    if (options.json !== false) {
      options.headers['Content-Type'] = 'application/json; charset=utf-8';
    }
    else {
      options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }
  }

  if (options.method.toLowerCase() === 'get' && options.cache !== true) {
    options.data._ = String(+new Date()) + String(Math.floor(Math.random() * 9999));
  }

  if (!options.onUploadProgress && options.onUploadPercent) {
    options.onUploadProgress = (progressEvent) => {
      if (progressEvent.lengthComputable) {
        options.onUploadPercent?.(Math.floor((progressEvent.loaded / progressEvent.total) * 100), progressEvent);
      }
    }
  }

  let loadingTimer;

  if (options.loadingStatus) {
    loadingTimer = setTimeout(() => {
      options.loadingStatus && (options.loadingStatus.value = true);
    }, 200);
  }

  let result: FetchResult<ResT>;

  try {
    const response = await instance(url, options);

    result = {
      isSuccess: response?.data?.code === 200,
      code: response?.data.code,
      data: response?.data?.data,
      msg: response?.data?.msg,
      $response: response,
    }
  }
  catch (error: any) {
    if (axios.isAxiosError(error)) {
      const response = error.response;

      result = {
        isSuccess: false,
        code: response?.data?.code ?? DEFAULT_ERROR_CODE,
        data: response?.data?.data,
        msg: response?.data?.msg || error.message,
        $response: response,
      }
    }
    else {
      result = {
        isSuccess: false,
        code: DEFAULT_ERROR_CODE,
        msg: error.message,
        $response: error,
      }
    }
  }

  if (options.loadingStatus) {
    clearTimeout(loadingTimer);
    options.loadingStatus.value = false;
  }

  if (!result.isSuccess) {
    if (import.meta.env.VITE_MOCK_LOGIN !== 'yes' && isAuthFailedCode(Number(result.code))) {
      clearTokenForClient();
      window.location.reload();
    }
    else {
      if (result.code === DEFAULT_ERROR_CODE) {
        Notification.error({
          content: i18n.global.t('settings.notification.networkError', { url, msg: result.msg }),
          duration: 60 * 1000,
          closable: true,
        });
      }
      else if (options.errorMessage !== false) {
        Notification.error({
          content: (options.errorMessage as string) || result.msg || 'Error!',
          duration: 60 * 1000,
          closable: true,
        });
      }
    }
  }

  return result;
}
